package com.igoal.dao;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.hibernate.Query;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.igoal.entity.ActionTypeEnum;
import com.igoal.entity.ComponentEnum;
import com.igoal.entity.PrivacyEnum;
import com.igoal.entity.PrivacySetting;
import com.igoal.entity.User;
import com.igoal.entity.UserActivity;
import com.igoal.entity.UserFriend;
import com.igoal.web.Constants;

@Repository("userActivityDAO")
public class UserActivityDAOImpl extends
		GenericDaoHibernate<UserActivity, Long> implements UserActivityDAO {
	
	private PrivacySettingDAO privacySettingDAO;

	public UserActivityDAOImpl() {
		super(UserActivity.class);
	}
	
	@Autowired
	public void setPrivacySettingDAO(PrivacySettingDAO privacySettingDAO) {
		this.privacySettingDAO = privacySettingDAO;
	}

	public List<UserActivity> listActivities(List<User> ignoreComponentOwners,
			List<User> actionOwners, List<ActionTypeEnum> actions,
			List<ComponentEnum> components, String orderProperty,
			Object lastOrderPropertyValue, int numberOfRecord, boolean before) {

		List<UserActivity> result = null;

		DetachedCriteria dc = DetachedCriteria.forClass(UserActivity.class);

		if (ignoreComponentOwners != null) {
			dc.add(Restrictions.not(Restrictions.in("componentOwner",
					ignoreComponentOwners)));
		}
		if (actionOwners != null) {
			dc.add(Restrictions.in("actionOwner", actionOwners));
		}

		dc.add(Restrictions.in("actionType", actions));
		dc.add(Restrictions.in("component", components));

		dc.addOrder(Order.desc(orderProperty));
		if (lastOrderPropertyValue != null) {
			if (before) {
				dc.add(Restrictions.gt(orderProperty, lastOrderPropertyValue));
			} else {
				dc.add(Restrictions.lt(orderProperty, lastOrderPropertyValue));
			}
		}

		if (numberOfRecord > 0) {
			result = findByCriteria(dc, 0, numberOfRecord);
		} else {
			result = findByCriteria(dc);
		}

		return result;
	}

	public List<UserActivity> listNotificationsLog(User componentOwner,
			User actionOwner, List<ActionTypeEnum> actions,
			List<ComponentEnum> components, String orderProperty,
			Object lastOrderPropertyValue, int numberOfRecord, boolean before) {
		List<UserActivity> result = null;

		DetachedCriteria dc = DetachedCriteria.forClass(UserActivity.class);
		if (componentOwner != null) {
			dc.add(Restrictions.eq("componentOwner", componentOwner));
			dc.add(Restrictions.ne("actionOwner", componentOwner));
		}
		if (actionOwner != null) {
			dc.add(Restrictions.eq("actionOwner", actionOwner));
		}
		dc.add(Restrictions.in("actionType", actions));
		dc.add(Restrictions.in("component", components));

		dc.addOrder(Order.desc(orderProperty));
		if (lastOrderPropertyValue != null) {
			if (before) {
				dc.add(Restrictions.gt(orderProperty, lastOrderPropertyValue));
			} else {
				dc.add(Restrictions.lt(orderProperty, lastOrderPropertyValue));
			}
		}

		if (numberOfRecord > 0) {
			result = findByCriteria(dc, 0, numberOfRecord);
		} else {
			result = findByCriteria(dc);
		}

		return result;
	}

	public int countNewNotificationLog(User componentOwner, User actionOwner,
			List<ActionTypeEnum> actions, List<ComponentEnum> components) {

		DetachedCriteria dc = DetachedCriteria.forClass(UserActivity.class);
		if (componentOwner != null) {
			dc.add(Restrictions.eq("componentOwner", componentOwner));
			dc.add(Restrictions.ne("actionOwner", componentOwner));
		}
		if (actionOwner != null) {
			dc.add(Restrictions.eq("actionOwner", actionOwner));
		}
		dc.add(Restrictions.in("actionType", actions));
		dc.add(Restrictions.in("component", components));
		dc.add(Restrictions.eq("read", false));

		return countByCriteria(dc);
	}

	public void markReadNotificationLog(User owner) {
		String hsql = "Update UserActivity n set n.read = :value where n.componentOwner.id = :cOwner";
		Query query = getSession().createQuery(hsql);
		query.setBoolean("value", true);
		query.setLong("cOwner", owner.getId());
		query.executeUpdate();
	}

	public List<UserActivity> getWallActivities(User requestUser,
			User wallOwner, List<ComponentEnum> components, Date updateBefore) {

		PrivacySetting publicPrivacy = privacySettingDAO
				.getPrivacy(PrivacyEnum.PUBLIC);
		PrivacySetting friendPrivacy = privacySettingDAO
				.getPrivacy(PrivacyEnum.FRIEND);
		List<PrivacySetting> privacyList = new ArrayList<PrivacySetting>();
		privacyList.add(publicPrivacy);
		privacyList.add(friendPrivacy);

		DetachedCriteria dc = DetachedCriteria.forClass(UserActivity.class);
		dc.add(Restrictions.in("component", components));
		
		if (requestUser == null) {
			// anonymous login
			return null;
		} else if (requestUser.equals(wallOwner)) {
			// my wall
			Set<UserFriend> ufs = requestUser.getFriends();
			Set<User> friends = new HashSet<User>();
			for (UserFriend uf : ufs) {
				friends.add(uf.getFrom());
			}
			Criterion e1 = Restrictions.or(Restrictions.eq("actionOwner", requestUser), Restrictions.eq("componentOwner", requestUser));
			if (friends.size() > 0) {
				Criterion e2 = Restrictions.and(Restrictions.or(Restrictions.in("actionOwner", friends), Restrictions.in("componentOwner", friends)),
						Restrictions.in("privacy", privacyList));
				dc.add(Restrictions.or(e1, e2));
			} else {
				dc.add(e1);
			}
		} else {
			// friend wall
			boolean isFriend = wallOwner.getFriends().contains(requestUser);
			if (!isFriend) {
				privacyList.remove(friendPrivacy);
			}

			dc.add(Restrictions.eq("actionOwner", wallOwner));
			dc.add(Restrictions.in("privacy", privacyList));
		}

		if (updateBefore != null) {
			dc.add(Restrictions.lt("updateDate", updateBefore));
		}
		dc.addOrder(Order.desc("updateDate"));

		List<UserActivity> result = findByCriteria(dc, 0,
				Constants.WALL_ACTIVITIES_PAGE_SIZE);

		return result;
	}
}
